package com.weed.jdbc;

import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashSet;
import java.util.Iterator;



import com.weed.core.WeedException;
import com.weed.helper.JDBCExceptionHelper;
import com.weed.helper.LogFactory;

import com.weed.common.Log;

/**
 * Manages prepared statements and batching.
 *
 * @author Gavin King
 */
public class AbstractBatcher
    implements Batcher {

  private static int globalOpenPreparedStatementCount;
  private static int globalOpenResultSetCount;

  private int openPreparedStatementCount;
  private int openResultSetCount;

  protected static final Log log = LogFactory.getLog(AbstractBatcher.class);
  protected static final Log SQL_LOG = LogFactory.getLog(AbstractBatcher.class);

  private PreparedStatement batchUpdate;
  private String batchUpdateSQL;

  private HashSet statementsToClose = new HashSet();
  private HashSet resultSetsToClose = new HashSet();
  private PreparedStatement lastQuery;

  private boolean releasing = false;

  private long transactionTimeout = -1;
  boolean isTransactionTimeoutSet;

  private Connection connection;

  public AbstractBatcher() {}

  /**
   * 设计处理事务超时时间。
   * @param seconds int 单位秒
   */
  public void setTransactionTimeout(int seconds) {
    isTransactionTimeoutSet = true;
    transactionTimeout = System.currentTimeMillis() / 1000 + seconds;
  }

  /**
   * 取消事务的处理超时设定。
   */
  public void unsetTransactionTimeout() {
    isTransactionTimeoutSet = false;
  }

  /**
   * 获得一个PreparedStatement.
   * @return PreparedStatement
   */
  protected PreparedStatement getStatement() {
    return batchUpdate;
  }

  /**
   * 获得一个CallableStatement，用于执行存储过程。
   * @param sql String
   * @return CallableStatement
   * @throws SQLException
   * @throws WeedException
   */
  public CallableStatement prepareCallableStatement(String sql) throws
      SQLException, WeedException {
    executeBatch();
    logOpenPreparedStatement();
    return getCallableStatement(connection, sql, false);
  }

  /**
   * 用于执行一个预处理的SQL语句。
   * @param sql String
   * @return PreparedStatement
   * @throws SQLException
   * @throws WeedException
   */
  public PreparedStatement prepareStatement(String sql) throws SQLException,
      WeedException {
    return prepareStatement(sql, false);
  }

  /**
   * 用于执行一个预处理的SQL语句。
   * @param sql String SQL语句
   * @param getGeneratedKeys boolean 主键
   * @return PreparedStatement 执行一个预处理的SQL语句
   * @throws SQLException SQL异常
   * @throws WeedException 其他异常
   */
  public PreparedStatement prepareStatement(String sql,
                                            boolean getGeneratedKeys) throws
      SQLException, WeedException {
    executeBatch();
    logOpenPreparedStatement();
    return getPreparedStatement(
        connection,
        sql,
        false,
        getGeneratedKeys,
        null,
        null,
        false
        );
  }

  /**
   * 用于执行一个预处理的SQL语句。
   * @param sql String
   * @return PreparedStatement
   * @throws SQLException
   * @throws WeedException
   */
  public PreparedStatement prepareSelectStatement(String sql) throws
      SQLException, WeedException {
    logOpenPreparedStatement();
    return getPreparedStatement(
        connection,
        sql,
        false,
        false,
        null,
        null,
        false
        );
  }

  public PreparedStatement prepareQueryStatement(
      String sql,
      boolean scrollable,
      ScrollMode scrollMode) throws SQLException, WeedException {
    logOpenPreparedStatement();
    PreparedStatement ps = getPreparedStatement(
        connection,
        sql,
        scrollable,
        scrollMode
        );
    setStatementFetchSize(ps);
    statementsToClose.add(ps);
    lastQuery = ps;
    return ps;
  }

  /**
   * 获得结果集。
   * @param ps PreparedStatement
   * @return ResultSet
   * @throws SQLException
   */
  public ResultSet getResultSet(PreparedStatement ps) throws SQLException {
    ResultSet rs = ps.executeQuery();
    resultSetsToClose.add(rs);
    logOpenResults();
    return rs;
  }
  /**
   * 获得结果集。
   * @param ps CallableStatement
   * @return ResultSet
   * @throws SQLException
   */
  public ResultSet getResultSet(CallableStatement ps) throws
      SQLException {
    boolean isResultSet = ps.execute();
    while (!isResultSet && ps.getUpdateCount() != -1) {
      isResultSet = ps.getMoreResults();
    }
    ResultSet rs = ps.getResultSet();
    resultSetsToClose.add(rs);
    logOpenResults();
    return rs;

  }
  /**
   * 关闭预处理语句。
   * @param ps PreparedStatement
   * @param rs ResultSet
   * @throws SQLException
   */
  public void closeQueryStatement(PreparedStatement ps, ResultSet rs) throws
      SQLException {
    statementsToClose.remove(ps);
    if (rs != null) resultSetsToClose.remove(rs);
    try {
      if (rs != null) {
        logCloseResults();
        rs.close();
      }
    }
    finally {
      closeQueryStatement(ps);
    }
  }
  /**
   *
   * @param sql String
   * @return PreparedStatement
   * @throws SQLException
   * @throws WeedException
   */
  public PreparedStatement prepareBatchStatement(String sql) throws
      SQLException, WeedException {
    sql = getSQL(sql);

    if (!sql.equals(batchUpdateSQL)) {
      batchUpdate = prepareStatement(sql); // calls executeBatch()
      batchUpdateSQL = sql;
    }
    else {
      log.debug("reusing prepared statement");
      log(sql);
    }
    return batchUpdate;
  }

  public CallableStatement prepareBatchCallableStatement(String sql) throws
      SQLException, WeedException {
    if (!sql.equals(batchUpdateSQL)) { // TODO: what if batchUpdate is a callablestatement ?
      batchUpdate = prepareCallableStatement(sql); // calls executeBatch()
      batchUpdateSQL = sql;
    }
    return (CallableStatement) batchUpdate;
  }

  public void executeBatch() throws WeedException {
    if (batchUpdate != null) {
      try {
        try {
          doExecuteBatch(batchUpdate);
        }
        finally {
          closeStatement(batchUpdate);
        }
      }
      catch (SQLException sqle) {
        throw JDBCExceptionHelper.convert(
            sqle,
            "Could not execute JDBC batch update",
            batchUpdateSQL
            );
      }
      finally {
        batchUpdate = null;
        batchUpdateSQL = null;
      }
    }
  }

  public void closeStatement(PreparedStatement ps) throws SQLException {
    logClosePreparedStatement();
    closePreparedStatement(ps);
  }

  private void closeQueryStatement(PreparedStatement ps) throws SQLException {

    try {
      //work around a bug in all known connection pools....
      if (ps.getMaxRows() != 0) ps.setMaxRows(0);
      if (ps.getQueryTimeout() != 0) ps.setQueryTimeout(0);
    }
    catch (Exception e) {
      log.warn("exception clearing maxRows/queryTimeout", e);
//			ps.close(); //just close it; do NOT try to return it to the pool!
      return; //NOTE: early exit!
    }
    finally {
      closeStatement(ps);
    }

    if (lastQuery == ps) lastQuery = null;

  }

  /**
   * Actually releases the batcher, allowing it to cleanup internally held
   * resources.
   */
  public void closeStatements() {
    try {
      releasing = true;

      try {
        if (batchUpdate != null) batchUpdate.close();
      }
      catch (SQLException sqle) {
        //no big deal
        log.warn("Could not close a JDBC prepared statement", sqle);
      }
      batchUpdate = null;
      batchUpdateSQL = null;

      Iterator iter = resultSetsToClose.iterator();
      while (iter.hasNext()) {
        try {
          logCloseResults();
          ( (ResultSet) iter.next()).close();
        }
        catch (SQLException e) {
          // no big deal
          log.warn("Could not close a JDBC result set", e);
        }
      }
      resultSetsToClose.clear();

      iter = statementsToClose.iterator();
      while (iter.hasNext()) {
        try {
          closeQueryStatement( (PreparedStatement) iter.next());
        }
        catch (SQLException e) {
          // no big deal
          log.warn("Could not close a JDBC statement", e);
        }
      }
      statementsToClose.clear();
    }
    finally {
      releasing = false;
    }
  }

  protected void doExecuteBatch(PreparedStatement ps) throws
      SQLException, WeedException{}

  private String preparedStatementCountsToString() {
    return
        " (open PreparedStatements: " +
        openPreparedStatementCount +
        ", globally: " +
        globalOpenPreparedStatementCount +
        ")";
  }

  private String resultSetCountsToString() {
    return
        " (open ResultSets: " +
        openResultSetCount +
        ", globally: " +
        globalOpenResultSetCount +
        ")";
  }

  private void logOpenPreparedStatement() {

    log.debug("about to open PreparedStatement" +
              preparedStatementCountsToString());
    openPreparedStatementCount++;
    globalOpenPreparedStatementCount++;

  }

  private void logClosePreparedStatement() {

    log.debug("about to close PreparedStatement" +
              preparedStatementCountsToString());
    openPreparedStatementCount--;
    globalOpenPreparedStatementCount--;

  }

  private void logOpenResults() {
    log.debug("about to open ResultSet" + resultSetCountsToString());
    openResultSetCount++;
    globalOpenResultSetCount++;
  }

  private void logCloseResults() {
    log.debug("about to close ResultSet" + resultSetCountsToString());
    openResultSetCount--;
    globalOpenResultSetCount--;
  }

  private void log(String sql) {
    log.debug("SQL:[" + sql + "]");
  }

  private String format(String sql) {
//    if (factory.getSettings().isFormatSqlEnabled()) {
//      return new Formatter(sql).format();
//    }
//    else {
//      return sql;
//    }
    return "";
  }

  /**
   * 获得预处理的Statement.
   * @param conn Connection
   * @param sql String
   * @param scrollable boolean
   * @param scrollMode ScrollMode
   * @return PreparedStatement
   * @throws SQLException
   */
  private PreparedStatement getPreparedStatement(
      final Connection conn,
      final String sql,
      final boolean scrollable,
      final ScrollMode scrollMode) throws SQLException {
    return getPreparedStatement(
        conn,
        sql,
        scrollable,
        false,
        null,
        scrollMode,
        false
        );
  }
  /**
   * 用于获得一个存储过程的调用。
   * @param conn Connection
   * @param sql String
   * @param scrollable boolean
   * @return CallableStatement
   * @throws SQLException
   */
  private CallableStatement getCallableStatement(
      final Connection conn,
      String sql,
      boolean scrollable) throws SQLException {
    sql = getSQL(sql);
    log(sql);
    log.trace("preparing callable statement");
    if (scrollable) {
      return conn.prepareCall(
          sql,
          ResultSet.TYPE_SCROLL_INSENSITIVE,
          ResultSet.CONCUR_READ_ONLY
          );
    }
    else {
      return conn.prepareCall(sql);
    }
  }
  /**
   * 获得SQL语句。
   * @param sql String
   * @return String
   */
  private String getSQL(String sql) {

    return sql;
  }

  /**
   * 获得预处理的SQL语句。
   * @param conn Connection 连接
   * @param sql String SQL语句。
   * @param scrollable boolean 执行方式(是否可以上下滚动)
   * @param useGetGeneratedKeys boolean 是否用主键
   * @param namedGeneratedKeys String[] 主健列表
   * @param scrollMode ScrollMode 是否滚动
   * @param callable boolean True用于调用存储过程 false批处理。
   * @return PreparedStatement 返回预处理
   * @throws SQLException SQL异常
   */
  private PreparedStatement getPreparedStatement(
      final Connection conn,
      String sql,
      boolean scrollable,
      final boolean useGetGeneratedKeys,
      final String[] namedGeneratedKeys,
      final ScrollMode scrollMode,
      final boolean callable) throws SQLException {
    sql = getSQL(sql);
    log(sql);
    log.trace("preparing statement");
    PreparedStatement result = null;
    if (scrollable) {
      if (callable) {
        result = conn.prepareCall(sql, scrollMode.toResultSetType(),
                                  ResultSet.CONCUR_READ_ONLY);
      }
      else {
        result = conn.prepareStatement(sql, scrollMode.toResultSetType(),
                                       ResultSet.CONCUR_READ_ONLY);
      }
    }
    else {
      if (callable) {
        result = conn.prepareCall(sql);
      }
      else {
        result = conn.prepareStatement(sql);
      }
    }
    return result;
  }

  private void setTimeout(PreparedStatement result) throws SQLException {
    if (isTransactionTimeoutSet) {
      int timeout = (int) (transactionTimeout -
                           (System.currentTimeMillis() / 1000));
      if (timeout <= 0) {
        throw new SQLException("transaction timeout expired");
      }
      else {
        result.setQueryTimeout(timeout);
      }
    }
  }

  private void closePreparedStatement(PreparedStatement ps) throws SQLException {
    try {
      log.trace("closing statement");
      ps.close();
//      if (factory.getStatistics().isStatisticsEnabled()) {
//        factory.getStatisticsImplementor().closeStatement();
//      }
    }
    finally {
      if (!releasing) {
        // If we are in the process of releasing, no sense
        // checking for aggressive-release possibility.

      }
    }
  }

  /**
   * 设置批处理的大小。
   * @param statement PreparedStatement
   * @throws SQLException
   */
  private void setStatementFetchSize(PreparedStatement statement) throws
      SQLException {
  }

  /**
   * 打开连接。
   * @return Connection
   * @throws WeedException
   */
  public Connection openConnection() throws WeedException {
    connection = null;//DatabasePool.getInstance().borrowConnection();
    return this.connection;
  }

  /**
   * 关闭连接。
   * @param conn Connection
   * @throws WeedException
   */
  public void closeConnection(Connection conn) throws WeedException {
     //DatabasePool.getInstance().returnConnection(conn);
  }

  public PreparedStatement prepareQueryStatement(String sql, boolean scrollable) throws
      SQLException, WeedException {
    return null;
  }


  public CallableStatement prepareCallableQueryStatement(String sql,
      boolean scrollable) throws SQLException, WeedException {
    return null;
  }

  public void addToBatch(Exception Exception) throws SQLException,
      WeedException {
  }

}

